package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.AppMapper;
import com.leyou.common.auth.entity.AppInfo;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.clients.UserClient;
import com.leyou.user.dto.UserDTO;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
@EnableConfigurationProperties(JwtProperties.class)
public class AuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties props;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private AppMapper appMapper;


    /**
     * 根据用户名密码，先校验，然后使用校验结果生成token
     *
     * @param username
     * @param password
     */
    public void login(String username, String password, HttpServletResponse response) {

        UserDTO userDTO = null;
        try {
            // 远程调用ly-user，查询用户信息
            userDTO = this.userClient.queryUserByNameAndPass(username, password);
            log.info("【授权中心】跨服务查询用户信息成功");
        } catch (FeignException e) {//FeignException feign请求 的具体的异常返回
            log.error("【授权中心】跨服务查询用户信息失败");
            throw new LyException(ExceptionEnum.REMOTE_SERVER_UNABLE);
        }

        if (null == userDTO) {
            throw new LyException(ExceptionEnum.INVALID_NAME_PASS);
        }

        UserInfo userInfo = BeanHelper.copyProperties(userDTO, UserInfo.class);

        //应该查询当前用户的角色然后赋值
        userInfo.setRole("假Role");

        //生成token，并存入cookie
        generateTokenAndAdd2Cookie(response, userInfo);

    }

    private void generateTokenAndAdd2Cookie(HttpServletResponse response, UserInfo userInfo) {




        //Token生成
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, props.getPrivateKey(), props.getUser().getExpire());

        log.info("【auth服务】生成token成功");

        Cookie cookie = new Cookie(props.getUser().getCookieName(), token);
        //cookie最大有效时间
        cookie.setMaxAge(props.getUser().getCookieMaxAge());
        //cookie绑给哪个域
        cookie.setDomain(props.getUser().getCookieDomain());
        //域内生效路径
        cookie.setPath(props.getUser().getCookiePath());
        //设置cookie，对于脚本，隐藏，提升安全，目前来说，禁止js访问这个cookie的值
        cookie.setHttpOnly(props.getUser().getCookieHttpOnly());

        //把cookie加入到response的头内
        response.addCookie(cookie);

        log.info("【auth服务】把token成功，存入cookie中");
    }

    public UserInfo verify(HttpServletResponse response, HttpServletRequest request) {

        String token = CookieUtils.getCookieValue(request, props.getUser().getCookieName());

        //获取解析token时，查验token的id是否在黑名单中，如果在，说明这是盗用，拒绝请求，同时删除cookie（银行发现假币没收）


        //解析token
        Payload<UserInfo> infoFromToken = getUserInfoPayload(token);

        //如果解析token时，当前tokenid存在于redis中，说明，是冒用
        if (redisTemplate.hasKey(infoFromToken.getId())) {

            //发现cookie是冒用时，删除
            deleteCookie(response);
            log.error("【auth服务】发现有人莫用token，已被删除");
            throw new LyException(ExceptionEnum.REQUEST_PARAM_ERROR);
        }

        UserInfo info = infoFromToken.getInfo();


        //如果生成的时间和现在比超过1min则，重新生成token
        if (new DateTime(infoFromToken.getExpiration()).minusMinutes(props.getUser().getExpire() - 1).isBeforeNow()) {

            //不仅仅要解析，同时要生成新的token以及新的cookie

            generateTokenAndAdd2Cookie(response, info);
        }

        return info;
    }


    public void logout(HttpServletResponse response, HttpServletRequest request) {

        String token = CookieUtils.getCookieValue(request, props.getUser().getCookieName());

        //解析token
        Payload<UserInfo> infoFromToken = getUserInfoPayload(token);

        //把不用的token的id存入redis
        DateTime expiration = new DateTime(infoFromToken.getExpiration());


        //剩余过期时间不足3s，选择不存储
        if (!expiration.minusSeconds(3).isBeforeNow()) {
            //把不用的token的id存入redis，存储的周期为，未来过期时间-当前时间的时间差，毫秒值
            redisTemplate.opsForValue().set(infoFromToken.getId(), "", expiration.getMillis() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        //获取此时cookie中的token，获取其id，把其id，存入黑名单，
        deleteCookie(response);
    }

    //解析token
    private Payload<UserInfo> getUserInfoPayload(String token) {
        Payload<UserInfo> infoFromToken = null;
        try {
            infoFromToken = JwtUtils.getInfoFromToken(token, props.getPublicKey(), UserInfo.class);
            log.info("【auth服务】解析用户信息成功");
        } catch (Exception e) {
            log.error("【auth服务】解析用户信息失败");
            throw new LyException(ExceptionEnum.REQUEST_PARAM_ERROR);
        }
        return infoFromToken;
    }

    //删除cookie
    private void deleteCookie(HttpServletResponse response) {
        //删除cookie，拿同名cookie覆盖之前的cookie
        Cookie cookie = new Cookie(props.getUser().getCookieName(), "");
        //cookie最大有效时间,0表示立即删除，-1表示随着浏览器关闭删除
        cookie.setMaxAge(0);
        //cookie绑给哪个域
        cookie.setDomain(props.getUser().getCookieDomain());
        //域内生效路径
        cookie.setPath(props.getUser().getCookiePath());
        //设置cookie，对于脚本，隐藏，提升安全，目前来说，禁止js访问这个cookie的值
        cookie.setHttpOnly(props.getUser().getCookieHttpOnly());

        //把cookie加入到response的头内
        response.addCookie(cookie);
    }

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public String authenticate(Long id, String secret) {

        ApplicationInfo applicationInfo = this.appMapper.selectByPrimaryKey(id);

        //如果服务不存在，护着，密码匹配不成功
        if (applicationInfo==null||!passwordEncoder.matches(secret,applicationInfo.getSecret())){
            throw new LyException(ExceptionEnum.REQUEST_PARAM_ERROR);
        }

        //封装token的载荷内容
        AppInfo appInfo = BeanHelper.copyProperties(applicationInfo, AppInfo.class);

        //根据当前服务id，查询其目标id
        List<Long> targetList = this.appMapper.selectTargetList(applicationInfo.getId());

        appInfo.setTargetList(targetList);


        //基于appInfo，生成token

        String token = JwtUtils.generateTokenExpireInMinutes(appInfo, props.getPrivateKey(), 1500);

        return token;
    }
}
